package com.mojavelinux.open18;

import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import javax.faces.application.FacesMessage;

import org.apache.commons.beanutils.PropertyUtils;
import org.appfuse.service.GenericManager;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Begin;
import org.jboss.seam.annotations.End;
import org.jboss.seam.annotations.Factory;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.WebRemote;
import org.jboss.seam.annotations.datamodel.DataModel;
import org.jboss.seam.annotations.datamodel.DataModelSelection;
import org.jboss.seam.core.Events;
import org.jboss.seam.core.FacesMessages;
import org.springframework.orm.ObjectRetrievalFailureException;

@Name( "courseListAction" )
@Scope( ScopeType.CONVERSATION )
public class CourseListAction implements Serializable {

	private static final long serialVersionUID = 1L;

	/**
	 * NOTE: during remote call, the FacesContext is <code>null</code>
	 * therefore we cannot resolve this Spring bean using the delegating variable resolver.
	 * Therefore, we use Spring beans exposed as Seam components.
	 */
	@In( create = true )
	private GenericManager<Course, Long> courseManager;
	
	@In
	private FacesMessages facesMessages;
	
	@DataModel
	private List<Course> courses;
	
	private int pageSize = 5;
	
	@DataModelSelection
	@Out(required = false)
	private Course selectedCourse;

	private SortState sortState;
	
	@WebRemote
	public List<Course> getCourses() {
		return courses;
	}
	
	@Begin
	@Factory( "courses" )
	public List<Course> fetchCourses() {
		sortState = new SortState( "name", true );
		courses = courseManager.getAll();
		resort();
		return courses;
	}
	
	public SortState getSortState() {
		return sortState;
	}
	
	public int getPageSize() {
		return pageSize;
	}

	public void setPageSize( int pageSize ) {
		this.pageSize = pageSize;
	}

	public void resort() {
		Collections.sort( courses, new Comparator<Course>() {

			public int compare( Course a, Course b ) {
				if ( sortState.getProperty() == null ) {
					return 0;
				}
				
				try {
					@SuppressWarnings("unchecked")
					Comparable<Object> valueA = (Comparable<Object>) PropertyUtils.getProperty( a, sortState.getProperty() );
					@SuppressWarnings("unchecked")
					Comparable<Object> valueB = (Comparable<Object>) PropertyUtils.getProperty( b, sortState.getProperty() );
					return sortState.isAscending() ? valueA.compareTo( valueB ) : valueB.compareTo( valueA );
				} catch ( Exception e ) {
					return 0;
				}
			}
			
		});
	}
	
	public void sortOn( String property ) {
		if ( sortState.getProperty().equals( property ) ) {
			sortState.setAscending( !sortState.isAscending() );
		}
		else {
			sortState.setAscending( true );
			sortState.setProperty( property );
		}
		
		resort();
	}

	// QUESTION: why are delete and add handled differently?
	public void delete() {
		try {
			courseManager.remove( selectedCourse.getId() );
			//courses.remove( selectedCourse );
			//sync( selectedCourse, "remove" );
			Events.instance().raiseEvent( "courseListChanged", selectedCourse, "remove" );
			facesMessages.add( "#{selectedCourse.name} has been removed from the database." );
		} catch ( ObjectRetrievalFailureException e ) {
			facesMessages.add( FacesMessage.SEVERITY_WARN, "The selected course has already been removed from the database." );
		}
	}
	
	@Observer( { "courseListChanged" } )
	public void sync( Course course, String operation ) {
		if ( course == null || operation == null ) {
			throw new IllegalArgumentException( "Expecting a course and an operation." );
		}
		
		if ( "add".equals( operation ) ) {
			courses.add( course );
			resort();
		}
		else if ( "remove".equals( operation ) ) {
			courses.remove( course );
			resort();
		}

	}
	
	/**
	 * Help the conversation process along a bit by clearing the courses
	 * manual.  When a postback action is configured to end a conversation,
	 * Seam isn't clearing courses (perhaps by design).  Thus, it is necessary
	 * to nuke it here.
	 */
	@Observer( "org.jboss.seam.endConversation" )
	public void reset() {
		courses = null;
	}
	
	@End
	public String end() {
		reset();
		return "success";
	}

}
